package com.dm.server.limiter;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;

/**
 * Author: Xhy
 * CreateTime: 2023-03-10 23:18
 * 滑动窗口
 */
@Component
public class SlideWindowsLimiter extends LimiterAbstract {
    @Resource
    private final StringRedisTemplate stringRedisTemplate;

    public SlideWindowsLimiter(StringRedisTemplate stringRedisTemplate) {
        this.stringRedisTemplate = stringRedisTemplate;
    }


    // 具体的逻辑
    @Override
    public boolean check(LimiterDTO restrictDTO) {

        return canAccess(1,1000);
    }

    static final String SLIDING_WINDOW= "SLIDING_WINDOW:";
    /**
     * 判断key的value中的有效访问次数是否超过最大限定值maxCount，若没超过，
     * 调用increment方法，将窗口内的访问数加一
     * 判断与数量增长同步处理
     *
     * @param windowInSecond 窗口间隔，秒
     * @param maxCount       最大计数
     * @return 可访问 or 不可访问
     */
    //10s内最多5条
    public boolean canAccess(int windowInSecond, long maxCount) {
        String key = SLIDING_WINDOW;

        //按key统计集合中的有效数量, 通过zset的key，得到所有的元素，再判断所有元素的个数，是否超过maxCount
        Long count = stringRedisTemplate.opsForZSet().zCard(key);

        if (count < maxCount) {
            //小于最大可以走最加入逻辑
            increment(key, windowInSecond);
            return false;
        } else {
            return true;
        }
    }

    /**
     * 滑动窗口计数增长
     *
     * @param key            redis key
     * @param windowInSecond 窗口间隔，秒
     */
                                    //窗口间隔是10s
    public void increment(String key, Integer windowInSecond) {

        //获取当前时间毫秒值
        long currentMs = System.currentTimeMillis();
        // 废弃窗口开始时间,用于删除废弃窗口        当前时间-窗口间隔，70s-60s=10s（这样你就能删除第一个10s内所有的数据了）
        long windowStartMs = currentMs - windowInSecond * 1000;


        // 单例模式(提升性能)，得到对象，不然你每次链式调用都创建对象，开启io浪费资源，直接走批处理
        ZSetOperations zSetOperations = stringRedisTemplate.opsForZSet();



        // 清除窗口过期成员                             //删除score为0-10的元素
        zSetOperations.removeRangeByScore(key, 0, windowStartMs);
        // 添加当前时间 value=当前时间戳 score=当前时间戳   //用时间戳当key不重复，值也是当前时间
        zSetOperations.add(key, String.valueOf(currentMs), currentMs);
        // 设置key过期时间
        stringRedisTemplate.expire(key, windowInSecond, TimeUnit.SECONDS);//最后为key设置过期时间
    }

}
